﻿using System;
using System.Collections.Generic;
using System.Linq;
using StockholmsStad.Data.Context;
using StockholmsStad.Data.ContractFactory;
using StockholmsStad.Data.Entity;
using StockholmsStad.Data.Interfaces;
using StockholmsStad.Runtime.Serialization.DataContract;
using StockholmsStad.Runtime.Serialization.DataContract.Enums;
using StockholmsStad.Utilities.Enums;
using StockholmsStad.Utilities.Exceptions;
using Couple=StockholmsStad.Runtime.Serialization.DataContract.Couple;
using InformationText = StockholmsStad.Runtime.Serialization.DataContract.InformationText;
using TimeBlock = StockholmsStad.Data.Entity.TimeBlock;

namespace StockholmsStad.Data.DatabaseAgents
{
    internal class MaintenanceAgent : DBAgentBase, IMaintenanceAgent
    {
        #region IMaintenanceAgent Members

        public void UpdateTimeBlock(IDataContext context, MinisterTimeBlock timeBlock)
        {
            try
            {
                var dbCtx = context.GetInternalContext();
                var entityTimeBlock = dbCtx.TimeBlocks.Where(t => t.TimeBlockId == timeBlock.TimeBlockId.Value).SingleOrDefault();

                if (timeBlock.Minister != null)
                    entityTimeBlock.MinisterId = timeBlock.Minister.MinisterId;

                if (timeBlock.TempMinister != null)
                    entityTimeBlock.TempMinisterId = timeBlock.TempMinister.MinisterId;

                entityTimeBlock.IntervalMinutes = timeBlock.Interval;
                entityTimeBlock.StartDateTime = timeBlock.StartDateTime;
                entityTimeBlock.TimeBlockStatus = timeBlock.TimeBlockStatus == TimeBlockStatus.Bookable ? 1 : 2;

                dbCtx.SubmitChanges();
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public int AddTimeBlock(IDataContext context, MinisterTimeBlock timeBlock)
        {
            try
            {
                var entityTimeBlock = new TimeBlock
                {
                    StartDateTime = timeBlock.StartDateTime,
                    IntervalMinutes = timeBlock.Interval,
                    TimeBlockStatus =
                        timeBlock.TimeBlockStatus == TimeBlockStatus.Bookable ? 1 : 2,
                };

                if (timeBlock.TimeBlockId.HasValue)
                    entityTimeBlock.TimeBlockId = timeBlock.TimeBlockId.Value;

                if (timeBlock.Minister != null)
                    entityTimeBlock.MinisterId = timeBlock.Minister.MinisterId;

                if (timeBlock.TempMinister != null)
                    entityTimeBlock.TempMinisterId = timeBlock.TempMinister.MinisterId;

                var dbCtx = context.GetInternalContext();
                return Insert(entityTimeBlock, dbCtx);
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e.InnerException);
            }
        }

        public List<MinisterTimeBlock> GetTimeBlocks(IDataContext context, DateTime date)
        {
            try
            {
                var endDate = date.AddHours(23).AddMinutes(59).AddSeconds(59);
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var factory = new MaintenanceFactory();
                List<MinisterTimeBlock> list =
                    dbCtx.TimeBlocks.OrderBy(t => t.StartDateTime).
                    Where(t => t.StartDateTime.CompareTo(date) >= 0 && t.StartDateTime.CompareTo(endDate) <= 0).
                    Select(t => factory.CreateContractObject(t)).ToList();
                return list;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public List<MinisterTimeBlock> GetTimeBlocks(IDataContext context)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var factory = new MaintenanceFactory();
                List<MinisterTimeBlock> list =
                    dbCtx.TimeBlocks.OrderBy(t => t.StartDateTime).Select(t => factory.CreateContractObject(t)).ToList();
                return list;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public List<ValueIdPair> GetTextTypes(IDataContext context)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var factory = new MaintenanceFactory();
                return dbCtx.InformationTextTypes.Select(t => factory.CreateContractTextObject(t)).ToList();
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }            
        }

        public InformationText GetText(IDataContext context, int textId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var factory = new MaintenanceFactory();
                return dbCtx.InformationTexts.Where(t => t.InformationTextId == textId).
                    Select(t => factory.CreateContractTextObject(t)).FirstOrDefault();
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public List<InformationText> GetTexts(IDataContext context, int textTypeId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var factory = new MaintenanceFactory();
                return dbCtx.InformationTexts.Where(t => t.TextTypeId == textTypeId).
                    Select(t => factory.CreateContractTextObject(t)).ToList();
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public int AddText(IDataContext context, InformationText text)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var factory = new MaintenanceFactory();
                var textType = dbCtx.InformationTextTypes.Where(t => t.InformationTextTypeId == text.TextType.Id).FirstOrDefault();

                if(textType == null)
                    throw new StockholmStadSystemException(SystemErrorCode.TextTypeNotExists);

                Entity.InformationText entityText = factory.CreateEntityTextObject(text, textType.InformationTextTypeId);
                return Insert(entityText, dbCtx);
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e.InnerException);
            }
        }

        public void UpdateText(IDataContext context, InformationText text)
        {
            var factory = new MaintenanceFactory();

            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var entity = dbCtx.InformationTexts.Where(t => t.InformationTextId == text.TextId.Value).SingleOrDefault();
                factory.UpdateEntityObject(text, entity);
                dbCtx.SubmitChanges();
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public void DeleteText(IDataContext context, InformationText text)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var factory = new MaintenanceFactory();
                var textType = dbCtx.InformationTextTypes.Where(t => t.InformationTextTypeId == text.TextType.Id).FirstOrDefault();

                if (textType == null)
                    throw new StockholmStadSystemException(SystemErrorCode.TextTypeNotExists);

                Entity.InformationText entityText = factory.CreateEntityTextObject(text, textType.InformationTextTypeId);
                Remove(entityText, dbCtx);
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e.InnerException);
            }
        }

        public List<ValueIdPair> GetAvailableLanguagesForReportDefinition(IDataContext context, MarriageCertificateType certificateType)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                var isWedding = certificateType == MarriageCertificateType.Wedding ? true : false;
                List<GetAvailableLanguagesForReportDefinitionResult> result = dbCtx.GetAvailableLanguagesForReportDefinition(isWedding).ToList();

                var list = new List<ValueIdPair>();

                foreach (var res in result)
                {
                    list.Add(new ValueIdPair { Id = res.LanguageId, Name = res.Name });
                }

                return list;
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e.InnerException);
            }
        }


        public List<ValueIdPair> GetLanguagesForCertificateType(IDataContext context, MarriageCertificateType certificateType)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                var isWedding = certificateType == MarriageCertificateType.Wedding ? true : false;
                List<GetReportDefinitionLanguagesResult> result = dbCtx.GetReportDefinitionLanguages(isWedding).ToList();

                var list = new List<ValueIdPair>();

                foreach(var res in result)
                {
                    list.Add(new ValueIdPair {Id = res.LanguageId, Name = res.Name});
                }

                return list;
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e.InnerException);
            }
        }

        public int AddMarriageCertificate(IDataContext context, MarriageCertificate certificate)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                var factory = new MaintenanceFactory();
                var reportDefinition = factory.CreateEntityObject(certificate.ReportDefinition);

                var reportId = Insert(reportDefinition, dbCtx);

                var marriageCert = new ReportMarriageCertificate
                                       {
                                           LanguageId = certificate.Language.Id,
                                           MarriageCertificateTypeMarriage =
                                               certificate.MarriageCertificateType == MarriageCertificateType.Wedding
                                                   ? true
                                                   : false,
                                           ReportTextFieldDefinitionID = reportId,
                                       };

                return Insert(marriageCert, dbCtx);
            }
            catch(Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e.InnerException);
            }
        }

        public void DeleteMarriageCertificate(IDataContext context, MarriageCertificate certificate)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                dbCtx.ReportMarriageCertificates.DeleteAllOnSubmit(
                    dbCtx.ReportMarriageCertificates.Where(
                        mc => mc.MarriageCertificateID == certificate.MarriageCertificateId)
                    );

                dbCtx.ReportTextFieldDefinitions.DeleteAllOnSubmit(
                    dbCtx.ReportTextFieldDefinitions.Where(
                        r => r.ReportTextFieldDefinitionID == certificate.ReportDefinition.ReportDefinitionId)
                    );

                dbCtx.SubmitChanges();
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e.InnerException);
            }
        }

        public void UpdateMarriageCertificate(IDataContext context, MarriageCertificate certificate)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var reportDef = dbCtx.ReportTextFieldDefinitions.Where(r => r.ReportTextFieldDefinitionID == certificate.ReportDefinition.ReportDefinitionId.Value).SingleOrDefault();

                // rapportens innehåll kan ändras, men inte certifikattypen och språket, så vi behöver endast uppdatera rapportdefinitionen. 
                if(reportDef != null)
                {
                    var factory = new MaintenanceFactory();
                    factory.UpdateEntityObject(reportDef, certificate.ReportDefinition);
                    dbCtx.SubmitChanges();
                }
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
            }

        }

        public MarriageCertificate GetMarriageCertificate(IDataContext context, MarriageCertificateType marriageCertificateType, int languageId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var factory = new MaintenanceFactory();

                bool wedding = marriageCertificateType == MarriageCertificateType.Wedding ? true : false;
                var cert =
                    dbCtx.ReportMarriageCertificates.Where(
                        m => m.MarriageCertificateTypeMarriage == wedding && m.LanguageId == languageId).FirstOrDefault();

                if (cert == null)
                    throw new StockholmStadSystemException(SystemErrorCode.ReportNotExists);

                return factory.CreateContractObject(cert);
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e.InnerException);
            }
        }

        public IList<Couple> GetCouplesForDepersonation(IDataContext context, DateTime fromDate, DateTime toDate)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                int? affectedCouples = -1;
                var coupleIds = dbCtx.CheckAndPerformDepersonation(null, 
                                                                 fromDate.ToShortDateString(),
                                                                 toDate.ToShortDateString(), 
                                                                 true, 
                                                                 ref affectedCouples);

                var coupleAgent = AgentFactory.GetCoupleAgent();
                var coupleList = new List<Couple>();
                foreach (var coupleId in coupleIds)
                {
                    var couple = coupleAgent.GetCouple(context, coupleId.CoupleId.Value);
                    coupleList.Add(couple);
                }

                return coupleList;

            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e.InnerException);
            }
        }

        public int DepersonateCouples(IDataContext context, IList<Couple> couples)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                int depersonatedCouples = 0;
                foreach (var couple in couples)
                {
                    int? affectedCouples = 0; 
                    dbCtx.CheckAndPerformDepersonation(couple.CoupleId,
                                                         null,
                                                         null,
                                                         false,
                                                         ref affectedCouples);

                    if(affectedCouples.HasValue)
                        depersonatedCouples += affectedCouples.Value;
                }

                dbCtx.SubmitChanges();

                return depersonatedCouples;

            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e.InnerException);
            }
        }

        /// <summary>
        /// Hämtar databasens version.
        /// </summary>
        /// <param name="context">Det databascontext som operationen köra i</param>
        /// <returns>En version <see cref="StockholmsStad.Data.Entity.Version"/></returns>
        public Entity.Version GetDatabaseVersion(IDataContext context)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                return dbCtx.Versions.OrderByDescending(v => v.VersionDate).FirstOrDefault();

            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e.InnerException);
            }
        }
        
        #endregion
    }
}